
package com.kugou.game.sdk.hotfix;

import com.kugou.game.sdk.persistence.preference.PrefUtil;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.util.Log;

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;

/**
 * 描述:提取Dex类
 *
 * @author jerryliu
 * @since 2016年3月15日 下午2:25:57
 */
final class MultiDexExtractor {
    private static final String TAG = "MultiDex";

    static List<File> load(Context context, ApplicationInfo applicationInfo, File dexDir,
            MultiDex.MultiDexType type) throws IOException {
        Log.d(TAG, "MultiDexExtractor.load(" + applicationInfo.sourceDir + ")");
        File sourceApk = new File(applicationInfo.sourceDir);
        List<File> files;
        try {
            files = loadExistingExtractions(context, sourceApk, dexDir, type);
        } catch (IOException var9) {
            Log.w(TAG,
                    "Failed to reload existing extracted secondary dex files, falling back to fresh extraction",
                    var9);
            files = performExtractions(sourceApk, dexDir, type);
        }

        if (files == null || files.size() == 0) {
            files = performExtractions(sourceApk, dexDir, type);
        }

        Log.d(TAG, "load found " + files.size() + " secondary dex files");
        return files;
    }

    static List<File> loadPatch(Context context, ApplicationInfo applicationInfo, File patchDir,
            int sdkVersionCode) throws IOException {
        Log.d(TAG, "MultiDexExtractor.loadPatch");
        List<File> files = null;
        try {
            files = loadExistingPatch(context, patchDir, sdkVersionCode);
        } catch (IOException var9) {
            Log.w(TAG,
                    "Failed to reload existing extracted patch files, falling back to fresh extraction",
                    var9);
        }
        Log.d(TAG, "load found " + files.size() + " patch files");
        return files;
    }

    private static List<File> loadExistingPatch(Context context, File patchDir, int sdkVersionCode)
            throws IOException {
        Log.d(TAG, "loading existing patch start...");
        ArrayList<File> files = new ArrayList<File>();
        if (!patchDir.exists() || patchDir.list().length == 0) {
            return files;
        }
        File[] patchs = patchDir.listFiles();
        File maxPatch = null;
        int maxPatchCode = -1;
        for (File file : patchs) {
            try {
                if (!file.getName().endsWith(".jar")) {
                    continue;
                }
                String fileName = file.getName().substring(0, file.getName().length() - 4);
                int versionCode = Integer.valueOf(fileName.split("_")[1]);
                if (versionCode != sdkVersionCode) {
                    file.delete();
                    continue;
                }
                int patchCode = Integer.valueOf(fileName.split("_")[2]);
                if (patchCode > maxPatchCode) {
                    maxPatchCode = patchCode;
                    maxPatch = file;
                }

            } catch (Exception e) {
                e.printStackTrace();
            }

        }
        if (maxPatch != null) {
            files.add(maxPatch);
            if (!verifyZipFile(maxPatch)) {
                Log.d(TAG, "Invalid zip file: " + maxPatchCode + "--" + maxPatch);
                maxPatch.delete();
                Log.d(TAG, "patch:" + maxPatchCode + " delete.. ");
                throw new IOException("Invalid ZIP file.");
            }
            PrefUtil.putUsePatchId(context, MultiDex.USEPATCHCODE, maxPatchCode);
        } else {
            PrefUtil.putUsePatchId(context, MultiDex.USEPATCHCODE, 0);
        }
        Log.d(TAG, "loading existing patch end...");
        return files;
    }

    private static List<File> loadExistingExtractions(Context context, File sourceApk, File dexDir,
            MultiDex.MultiDexType type) throws IOException {
        Log.d(TAG, "loading existing secondary dex files");
        String extractedFilePrefix = sourceApk.getName();
        MultiDex.MultiDexType[] typeArray = new MultiDex.MultiDexType[] {
            type
        };
        ArrayList<File> files = new ArrayList<File>(typeArray.length);
        for (int i = 0; i < typeArray.length; i++) {
            String fileName = extractedFilePrefix + "." + typeArray[i].getName() + ".zip";
            File extractedFile = new File(dexDir, fileName);

            if (!extractedFile.isFile()) {
                Log.d(TAG, "check file in assets");
                try {
                    context.getAssets().open(typeArray[i].getName() + ".jar");
                } catch (Exception e) {
                    Log.d(TAG, "check file in assets by open failed");
                    continue;
                }
                Log.d(TAG, "check file in assets by open success");
                throw new IOException("Missing extracted secondary dex file \'"
                        + extractedFile.getPath() + "\'");
            }

            files.add(extractedFile);
            if (!verifyZipFile(extractedFile)) {
                Log.d(TAG, "Invalid zip file: " + extractedFile);
                throw new IOException("Invalid ZIP file.");
            }
        }
        return files;
    }

    private static List<File> performExtractions(File sourceApk, File dexDir,
            MultiDex.MultiDexType type) throws IOException {
        String extractedFilePrefix = sourceApk.getName();
        if (MultiDex.MultiDexType.HACK == type) {
            prepareDexDir(dexDir, extractedFilePrefix);
        }
        ArrayList<File> files = new ArrayList<File>();
        ZipFile apk = new ZipFile(sourceApk);
        try {

            ArrayList<MultiDex.MultiDexType> types = new ArrayList<MultiDex.MultiDexType>();
            types.add(type);
            int e = 0;
            for (int i = 0; i < types.size(); i++) {
                ZipEntry dexFile = apk.getEntry("assets/" + types.get(i).getName() + ".jar");
                if (dexFile == null) {
                    continue;
                }
                String fileName = extractedFilePrefix + "." + types.get(i).getName() + ".zip";
                File extractedFile = new File(dexDir, fileName);
                files.add(extractedFile);
                Log.d(TAG, "Extraction is needed for file " + extractedFile);
                int numAttempts = 0;
                boolean isExtractionSuccessful = false;

                while (numAttempts < 3 && !isExtractionSuccessful) {
                    ++numAttempts;
                    extract(apk, dexFile, extractedFile, extractedFilePrefix);
                    isExtractionSuccessful = verifyZipFile(extractedFile);
                    Log.d(TAG,
                            "Extraction " + (isExtractionSuccessful ? "success" : "failed")
                                    + " - length " + extractedFile.getAbsolutePath() + ": "
                                    + extractedFile.length());
                    if (!isExtractionSuccessful) {
                        extractedFile.delete();
                        if (extractedFile.exists()) {
                            Log.w(TAG, "Failed to delete corrupted secondary dex \'"
                                    + extractedFile.getPath() + "\'");
                        }
                    }
                }

                if (!isExtractionSuccessful) {
                    throw new IOException("Could not create zip file "
                            + extractedFile.getAbsolutePath() + " for secondary dex (" + e + ")");
                }

            }
        } finally {
            try {
                apk.close();
            } catch (IOException var16) {
                Log.w(TAG, "Failed to close resource", var16);
            }
        }
        return files;
    }

    private static void prepareDexDir(File dexDir, final String extractedFilePrefix)
            throws IOException {
        File cache = dexDir.getParentFile();
        mkdirChecked(cache);
        mkdirChecked(dexDir);
        File[] files = dexDir.listFiles();
        if (files == null) {
            Log.w(TAG, "Failed to list secondary dex dir content (" + dexDir.getPath() + ").");
        } else {
            File[] arr$ = files;
            int len$ = files.length;

            for (int i$ = 0; i$ < len$; ++i$) {
                File oldFile = arr$[i$];
                Log.d(TAG,
                        "Trying to delete old file " + oldFile.getPath() + " of size "
                                + oldFile.length());
                if (!oldFile.delete()) {
                    Log.w(TAG, "Failed to delete old file " + oldFile.getPath());
                } else {
                    Log.d(TAG, "Deleted old file " + oldFile.getPath());
                }
            }

        }
    }

    private static void mkdirChecked(File dir) throws IOException {
        dir.mkdir();
        if (!dir.isDirectory()) {
            File parent = dir.getParentFile();
            if (parent == null) {
                Log.w(TAG, "Failed to create dir " + dir.getPath() + ". Parent file is null.");
            } else {
                Log.w(TAG, "Failed to create dir " + dir.getPath() + ". parent file is a dir "
                        + parent.isDirectory() + ", a file " + parent.isFile() + ", exists "
                        + parent.exists() + ", readable " + parent.canRead() + ", writable "
                        + parent.canWrite());
            }

            throw new IOException("Failed to create cache directory " + dir.getPath());
        }
    }

    private static void extract(ZipFile apk, ZipEntry dexFile, File extractTo,
            String extractedFilePrefix) throws IOException, FileNotFoundException {
        File tmp = File.createTempFile(extractedFilePrefix, ".zip", extractTo.getParentFile());
        Log.d(TAG, "Extracting " + tmp.getPath());

        InputStream in = apk.getInputStream(dexFile);
        FileOutputStream out = new FileOutputStream(tmp);
        try {
            try {
                byte[] buffer = new byte[16384];
                for (int length = in.read(buffer); length != -1; length = in.read(buffer)) {
                    out.write(buffer, 0, length);
                }
            } finally {
                out.close();
            }
            Log.d(TAG, "Renaming to " + extractTo.getPath());
            if (!tmp.renameTo(extractTo)) {
                try {
                    copyFile(tmp, extractTo);
                } catch (Exception e) {
                    throw new IOException("Failed to rename \"" + tmp.getAbsolutePath()
                            + "\" to \"" + extractTo.getAbsolutePath() + "\"");
                }
            }
        } finally {
            closeQuietly(in);
            tmp.delete();
        }

    }

    public static void copyFile(File srcFile, File destFile) throws IOException {
        doCopyFile(srcFile, destFile, true);
    }

    public static final long ONE_KB = 1024;

    /**
     * The number of bytes in a megabyte.
     */
    public static final long ONE_MB = ONE_KB * ONE_KB;

    /**
     * The file copy buffer size (10 MB) （原为30MB，为更适合在手机上使用，将其改为10MB，by
     * Geek_Soledad)
     */
    private static final long FILE_COPY_BUFFER_SIZE = ONE_MB * 10;

    /**
     * Internal copy file method.
     * 
     * @param srcFile the validated source file, must not be <code>null</code>
     * @param destFile the validated destination file, must not be
     *            <code>null</code>
     * @param preserveFileDate whether to preserve the file date
     * @throws IOException if an error occurs
     */
    private static void doCopyFile(File srcFile, File destFile, boolean preserveFileDate)
            throws IOException {
        if (destFile.exists() && destFile.isDirectory()) {
            throw new IOException("Destination '" + destFile + "' exists but is a directory");
        }

        FileInputStream fis = null;
        FileOutputStream fos = null;
        FileChannel input = null;
        FileChannel output = null;
        try {
            fis = new FileInputStream(srcFile);
            fos = new FileOutputStream(destFile);
            input = fis.getChannel();
            output = fos.getChannel();
            long size = input.size();
            long pos = 0;
            long count = 0;
            while (pos < size) {
                count = (size - pos) > FILE_COPY_BUFFER_SIZE ? FILE_COPY_BUFFER_SIZE : (size - pos);
                pos += output.transferFrom(input, pos, count);
            }
        } finally {
            closeQuietly(output);
            closeQuietly(fos);
            closeQuietly(input);
            closeQuietly(fis);
        }

        if (srcFile.length() != destFile.length()) {
            throw new IOException("Failed to copy full contents from '" + srcFile + "' to '"
                    + destFile + "'");
        }
        if (preserveFileDate) {
            destFile.setLastModified(srcFile.lastModified());
        }
    }

    public static boolean verifyZipFile(File file) {
        try {
            ZipFile ex = new ZipFile(file);
            try {
                ex.close();
                return true;
            } catch (IOException var3) {
                Log.w(TAG, "Failed to close zip file: " + file.getAbsolutePath());
            }
        } catch (ZipException var4) {
            Log.w(TAG, "File " + file.getAbsolutePath() + " is not a valid zip file.", var4);
        } catch (IOException var5) {
            Log.w(TAG, "Got an IOException trying to open zip file: " + file.getAbsolutePath(),
                    var5);
        }

        return false;
    }

    private static void closeQuietly(Closeable closeable) {
        try {
            closeable.close();
        } catch (IOException var2) {
            Log.w(TAG, "Failed to close resource", var2);
        }

    }
}
